/*The MIT License

Copyright (c) <2008> <Samir Menon>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.*/

#include "CNeuronalNetwork.h"

namespace bis_neuron
{
	
using namespace std;

CNeuronalNetwork::CNeuronalNetwork(const unsigned int & arg_num_ctx_neurons,
									 								 const unsigned int & arg_num_bg_neurons,
									 								 const string & arg_spike_outfilename,
																	 const string & arg_data_outfilename)
{	
	//Setup neurons	
	num_ctx_neurons = arg_num_ctx_neurons;
	num_bg_neurons = arg_num_bg_neurons;
	ctx_neurons = new SCtxNeuron [arg_num_ctx_neurons];
	bg_neurons = new SBgNeuron [arg_num_bg_neurons];
	
	//Setup network parameters	
	network_clock = CSystemClock::get_clock();
	sp_spool = new CSpikeSpooler(arg_spike_outfilename);
	data_spool = new CSpikeSpooler(arg_data_outfilename);
//	data_spool = NULL;
	assert(network_clock!=NULL);
}


CNeuronalNetwork::~CNeuronalNetwork()
{	
	if(sp_spool!=NULL){	delete sp_spool;	}
	if(data_spool!=NULL){	delete data_spool;	}
	if(ctx_neurons!=NULL){	delete [] ctx_neurons;	}
	if(bg_neurons!=NULL){	delete [] bg_neurons;	}
}

//****************************************
// 1. The cortex-bg loop
//****************************************

void CNeuronalNetwork::initialize_ctx2bg()
{
	unsigned int i,i0,iend,j,k;
	
	for(i=0;i<num_ctx_neurons;i++)
	{	
		ctx_neurons[i].stdp_neuron->initialize(i,sp_spool, NULL);
		ctx_neurons[i].stdp_neuron->set_r(1.35);
		ctx_neurons[i].stdp_neuron->set_exec_flag_on();
	}
	
	for(i=0;i<num_bg_neurons;i++)
	{	
		bg_neurons[i].da_neuron->initialize(i,sp_spool, data_spool);
		bg_neurons[i].da_neuron->set_r(0.4 + (((float)i+1.0)/(float)num_bg_neurons) );//0.4-1.4
		bg_neurons[i].da_neuron->set_exec_flag_on();
	}
	
	ctx_per_bg = (unsigned int)(num_ctx_neurons/num_bg_neurons);
	
	for(j=0;j<num_bg_neurons;j++)
	{
		k=0;
		i0 = j*ctx_per_bg;
		iend = i0+ctx_per_bg;
		for(i=i0;i<iend;i++)
		{//Establish a connection between ctx[i] and da[j]
			//One da neuron gets synapses from about 10 ctx neurons
			assert(k<NUM_BG_SYNAPSES);
			ctx_neurons[i].conn_to[0] = j;//neuron(i)->neuron(j)
			bg_neurons[j].da_neuron->set_da_synapse(i,k, 0.1, 0.2);//neuron(i)->syn(k)[0.2:0.1]			
			k++;//move to next synapse
		}
	}
	assert(i==num_ctx_neurons);
	assert(j==num_bg_neurons);
}

void CNeuronalNetwork::run_ctx2bg()
{
	unsigned int i,i_ctx,i_bg;
	assert(ctx_per_bg == (unsigned int)(num_ctx_neurons/num_bg_neurons));
	for(i=0;i<NUM_CYCLES; i++)
	{
		for(i_ctx=0;i_ctx<num_ctx_neurons; i_ctx++){
			if(ctx_neurons[i_ctx].stdp_neuron->fire()) 
			{ bg_neurons[ctx_neurons[i_ctx].conn_to[0]].
				da_neuron->activate_da_synapse(i_ctx%ctx_per_bg);	
			}
		}
		for(i_bg=0;i_bg<num_bg_neurons; i_bg++){
			bg_neurons[i_bg].da_neuron->fire();
		}		
		network_clock->tick();
	}	
}

//****************************************
// 2. A simple bistability simulation
//****************************************
void CNeuronalNetwork::initialize_bgbistable()
{
	unsigned int i,j;
	assert(0==num_ctx_neurons);
	assert(0<num_bg_neurons);	
	
	for(i=0;i<num_bg_neurons;i++)
	{	
		bg_neurons[i].da_neuron->initialize(i,sp_spool, data_spool);
		//0.4-1.4
		bg_neurons[i].da_neuron->set_r(0.4 + (((float)i+1.0)/(float)num_bg_neurons) );
		bg_neurons[i].da_neuron->set_exec_flag_on();
		//Now set the synapses
		for(j=0;j<NUM_BG_SYNAPSES;j++)
		{
			//neuron(0)->syn(j)[pot-0.2 : npot-0.1]
			bg_neurons[i].da_neuron->set_da_synapse(j,0, 0.01, 0.02);			
		}
	}
	
	assert(i==num_bg_neurons);
}

void CNeuronalNetwork::run_bgbistable()
{
	/*
	unsigned int i;
	for(i=0;i<NUM_CYCLES; i++)
	{
		for(i_ctx=0;i_ctx<num_ctx_neurons; i_ctx++){
			if(ctx_neurons[i_ctx].stdp_neuron->fire()) 
			{ bg_neurons[ctx_neurons[i_ctx].conn_to[0]].
				da_neuron->activate_da_synapse(i_ctx%ctx_per_bg);	
			}
		}
		for(i_bg=0;i_bg<num_bg_neurons; i_bg++){
			bg_neurons[i_bg].da_neuron->fire();
		}		
		network_clock->tick();
	}	*/
}

//****************************************
// 3. A simple cubic neuron
// a)Resting  b)Excitable  c)Periodic Firing
//****************************************
void CNeuronalNetwork::initialize_simpleneuron(const float& arg_r)
{
	assert(1==num_ctx_neurons);
	assert(0==num_bg_neurons);	
	
	r = arg_r; sigma = 1.0; n = 1.0;
	tau_k = 0.01; delta_gk = 0.0;
	tau_ca = 0.01; r_ca = 0.0; delta_i_ca = 0.0;
	
	ctx_neurons[0].stdp_neuron->initialize(0,sp_spool, data_spool);
	ctx_neurons[0].stdp_neuron->set_data(r, sigma, n,
																		tau_k, delta_gk,
																		tau_ca, r_ca, delta_i_ca);
																		
	ctx_neurons[0].stdp_neuron->set_exec_flag_on();	
}
	

void CNeuronalNetwork::run_simpleneuron()
{
	unsigned int i;	
	for(i=0;i<NUM_CYCLES_SIMPLENEURON; i++)
	{
		ctx_neurons[0].stdp_neuron->fire();				
		network_clock->tick();
	}
	ctx_neurons[0].stdp_neuron->reset_neuron();
	ctx_neurons[0].stdp_neuron->set_r(0.663);
	for(i=0;i<NUM_CYCLES_SIMPLENEURON; i++)
	{
		ctx_neurons[0].stdp_neuron->fire();				
		network_clock->tick();
	}
	ctx_neurons[0].stdp_neuron->reset_neuron();
	ctx_neurons[0].stdp_neuron->set_r(0.666);
	for(i=0;i<NUM_CYCLES_SIMPLENEURON; i++)
	{
		ctx_neurons[0].stdp_neuron->fire();				
		network_clock->tick();
	}
	ctx_neurons[0].stdp_neuron->reset_neuron();
	ctx_neurons[0].stdp_neuron->set_r(0.67);
	for(i=0;i<NUM_CYCLES_SIMPLENEURON; i++)
	{
		ctx_neurons[0].stdp_neuron->fire();				
		network_clock->tick();
	}	
}

//****************************************
// 4. A simple K-Na neuron
// a)Super-Threshold Na
//****************************************
void CNeuronalNetwork::initialize_knaneuron(const float& arg_tau_k, 
																						const float& arg_delta_gk)
{
	assert(1==num_ctx_neurons);
	assert(0==num_bg_neurons);	
	
	r = THRESHOLD_R+0.2; sigma = 1.0; n = 1.0;
	tau_k = arg_tau_k; delta_gk = arg_delta_gk;
	tau_ca = 0.01; r_ca = 0.0; delta_i_ca = 0.0;
	
	ctx_neurons[0].stdp_neuron->initialize(0,sp_spool, data_spool);
	ctx_neurons[0].stdp_neuron->set_data(r, sigma, n,
																		tau_k, delta_gk,
																		tau_ca, r_ca, delta_i_ca);																		
	ctx_neurons[0].stdp_neuron->set_exec_flag_on();
}
	

void CNeuronalNetwork::run_knaneuron()
{
	float tau=50.0,delta=0.3;
	unsigned int i;
	for(tau=5;tau<=9; tau = tau+1)//For tau_k
	{
		for(delta=3.0;delta<=7.0;delta=delta+1)//For delta_gk
		{
			for(i=0;i<NUM_CYCLES_KNANEURON; i++)
				{
					ctx_neurons[0].stdp_neuron->fire();				
					network_clock->tick();
				}
				ctx_neurons[0].stdp_neuron->reset_neuron();
				ctx_neurons[0].stdp_neuron->set_data(r, sigma, n,
																					tau_k+(tau*10.0), delta_gk+(delta/10.0),
																					tau_ca, r_ca, delta_i_ca);
		}
	}	
}

//****************************************
// 5. A bursting Ca-K-Na neuron
// a)Super-Threshold Na
// b)Modulate Ica
//****************************************
void CNeuronalNetwork::initialize_caknaneuron(const float& arg_tau_ca, 
																						const float& arg_delta_i_ca)
{
	assert(1==num_ctx_neurons);
	assert(0==num_bg_neurons);	
	
	r = THRESHOLD_R+0.2; sigma = 1.0; n = 1.0;
	tau_k = 50; delta_gk = 0.4;
	tau_ca = arg_tau_ca; r_ca = 1.0; delta_i_ca = arg_delta_i_ca;
	
	ctx_neurons[0].stdp_neuron->initialize(0,sp_spool, data_spool);
	ctx_neurons[0].stdp_neuron->set_data(r, sigma, n,
																		tau_k, delta_gk,
																		tau_ca, r_ca, delta_i_ca);																		
	ctx_neurons[0].stdp_neuron->set_exec_flag_on();
}
	

void CNeuronalNetwork::run_caknaneuron()
{
	float tauk=1.0, tau=1.0,delta=3.0;
	unsigned int i;
	//Bursting in loop 6,7 (tau_k = 70, tau_ca,delta_i_ca=(10,.25),(20,.15)) 
	//loop 10(tau_k = 90, tau_ca,delta_i_ca=(10,.25)) BEST
	//loop 11,12 (tau_k = 90, tau_ca=20, both delta_i_ca) 
	for(tauk=0;tauk<3; tauk = tauk+1)//For tau_k 0,1,2
	{
		for(tau=0;tau<2; tau = tau+1)//For tau_ca 0,1
		{
			for(delta=0.0;delta<2.0;delta=delta+1)//For delta_i_ca 0,1
			{
				ctx_neurons[0].stdp_neuron->reset_neuron();
				ctx_neurons[0].stdp_neuron->set_data(r, sigma, n,
																				tau_k+(tauk*20.0), delta_gk,
																				tau_ca+(tau*10.0), r_ca, 
																				delta_i_ca+(delta/10.0));
				for(i=0;i<NUM_CYCLES_KNANEURON; i++)
					{
						ctx_neurons[0].stdp_neuron->fire();				
						network_clock->tick();
					}					
			}
		}
	}	
}


//****************************************
// 6. A bistable Ca-K-Na neuron
// a)Super-Threshold Na
// b)Modulate Ica
//****************************************
void CNeuronalNetwork::initialize_bistableneuron()
{
	assert(0==num_ctx_neurons);
	assert(1==num_bg_neurons);	
	
	r = THRESHOLD_R+0.2; sigma = 1.0; n = 1.0;
	tau_k = 70; delta_gk = 0.0;
	tau_ca = 0.01; r_ca = 1.0; delta_i_ca = 0.0;
	
	bg_neurons[0].da_neuron->initialize(0,sp_spool, data_spool);
	bg_neurons[0].da_neuron->set_data(r, sigma, n,
																		tau_k, delta_gk,
																		tau_ca, r_ca, delta_i_ca);																		
	bg_neurons[0].da_neuron->set_exec_flag_on();
}
	
void CNeuronalNetwork::run_bistableneuron()
{
	float tauk=1.0, tau=1.0,delta=3.0;
	unsigned int i;
	for(tauk=0;tauk<3; tauk = tauk+1)//For tau_k
	{
		for(tau=0;tau<2; tau = tau+1)//For tau_ca
		{
			for(delta=0.0;delta<2.0;delta=delta+1)//For delta_i_ca
			{
				for(i=0;i<NUM_CYCLES_KNANEURON; i++)
					{
						ctx_neurons[0].stdp_neuron->fire();				
						network_clock->tick();
					}
					bg_neurons[0].da_neuron->reset_neuron();
					bg_neurons[0].da_neuron->set_data(r, sigma, n,
																						tau_k+(tauk*20.0), delta_gk,
																						tau_ca+(tau*10.0), r_ca, 
																						delta_i_ca+(delta/10.0));
			}
		}
	}	
}



}
